/*
 * MIT License
 *
 * Copyright (c) 2020 Mr.css
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

package cn.seaboot.openapi.base;

import cn.seaboot.commons.core.CommonUtils;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Controller 预解析数据
 *
 * @author Mr.css
 * @version 2021-04-01 16:52
 */
public class ControllerInfo {
    /**
     * Controller 的 Class
     */
    @NotNull
    private Class<?> target;
    /**
     * RequestMapping
     */
    @Nullable
    private RequestMapping requestMapping;
    /**
     * 请求 url，为了减少代码判断，每一个 url 都是以 '/' 开始，
     * 如果未指定 url，则是一个空字符串，这个数组不会为空，长度也不会为 0。
     */
    @NotNull
    private String[] requestPath;
    /**
     * 接口描述
     */
    @Nullable
    private String controllerDescription;
    /**
     * 有效的 MethodInfo，一些不规范的代码，可能会把很多功能函数直接写在 Controller，这里已经删除了多余的 Method
     */
    @NotNull
    private List<MethodInfo> methodInfos = new ArrayList<>();

    public ControllerInfo(@NotNull Class<?> target, @Nullable RequestMapping requestMapping) {
        this.target = target;
        this.requestMapping = requestMapping;

        // 扫描 url
        this.analyzeRequestPath();
        // 扫描方法
        this.analyzeMethodInfo();
        // 扫描接口描述
        this.analyzeControllerDescription();
    }

    public ControllerInfo(Class<?> target) {
        this(target, target.getDeclaredAnnotation(RequestMapping.class));
    }

    /**
     * 扫描 url，不为空的情况下，全部以 '/' 开头，如果为空，则用空字符串。
     * <p>
     * Analyze request-path of RequestMapping，
     * if path is undefined, use empty String.
     */
    private void analyzeRequestPath() {
        if (requestMapping == null) {
            this.requestPath = new String[]{""};
        } else {
            this.requestPath = requestMapping.path();
            if (CommonUtils.isEmpty(this.requestPath)) {
                this.requestPath = requestMapping.value();
                for (int i = 0; i < this.requestPath.length; i++) {
                    String str = this.requestPath[i];
                    if (str.charAt(0) != '/') {
                        this.requestPath[i] = '/' + str;
                    }
                }
            }
            if (CommonUtils.isEmpty(this.requestPath)) {
                this.requestPath = new String[]{""};
            }
        }
    }

    /**
     * 扫描接口注释，缺省值是类名
     * <p>
     * Analyze description form Controller, if {@link Tag#description()} is undefined, will use classname
     */
    private void analyzeControllerDescription() {
        Tag tag = this.target.getDeclaredAnnotation(Tag.class);
        if (tag != null) {
            this.controllerDescription = tag.name();
        } else {
            this.controllerDescription = this.target.getSimpleName();
        }
    }

    /**
     * 获取 @RequestMapping 注解，兼容 restful 场景。
     * <p>
     * Get @RequestMapping instance form method.
     *
     * @param method method
     * @return RequestMapping
     */
    private RequestMapping getMethodRequestMapping(Method method) {
        Annotation[] annotations = method.getAnnotations();
        if (CommonUtils.isNotEmpty(annotations)) {
            for (Annotation annotation : annotations) {
                if (annotation.annotationType().equals(RequestMapping.class)) {
                    return (RequestMapping) annotation;
                } else {
                    RequestMapping requestMapping = annotation.getClass().getAnnotation(RequestMapping.class);
                    if (requestMapping != null) {
                        return requestMapping;
                    }
                }
            }
        }
        return null;
    }

    /**
     * 扫描所有的方法
     * <p>
     * Analyze all method controller, if method is marked @RequestMapping, will analyze as {@link MethodInfo}
     */
    private void analyzeMethodInfo() {
        Method[] methods = this.target.getDeclaredMethods();
        if (CommonUtils.isNotEmpty(methods)) {
            for (Method method : methods) {
                RequestMapping requestMapping = getMethodRequestMapping(method);
                if (requestMapping != null) {
                    this.methodInfos.add(new MethodInfo(method, requestMapping));
                }
            }
        }
    }

    //getter/setter-----------------------------------

    public Class<?> getTarget() {
        return target;
    }

    public void setTarget(Class<?> target) {
        this.target = target;
    }

    public RequestMapping getRequestMapping() {
        return requestMapping;
    }

    public void setRequestMapping(RequestMapping requestMapping) {
        this.requestMapping = requestMapping;
    }

    public String[] getRequestPath() {
        return requestPath;
    }

    public void setRequestPath(String[] requestPath) {
        this.requestPath = requestPath;
    }

    public void setControllerDescription(String controllerDescription) {
        this.controllerDescription = controllerDescription;
    }

    public String getControllerDescription() {
        return this.controllerDescription;
    }

    public List<MethodInfo> getMethodInfos() {
        return methodInfos;
    }

    public void setMethodInfos(List<MethodInfo> methodInfos) {
        this.methodInfos = methodInfos;
    }
}
